﻿using System;
using System.Collections.Generic;
using System.Windows.Forms;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using VoxelModel.Cameras;

namespace VoxelModel
{
    public struct Block
    {
        private bool visible;

        private Color _color;

        public Block(bool visible)
        {
            this.visible = visible;
            _color = Color.Black;
        }

        public Block(Color color)
        {
            visible = true;
            _color = color;
        }

        public bool Visible
        {
            get { return visible; }
            set { visible = value; }
        }

        public Color Color
        {
            get { return _color; }
            set { _color = value; }
        }
    }

    public class MainCavas : GraphicsDeviceControl
    {
        private ContentManager Content;

        private SpriteBatch spriteBatch;

        private SpriteFont UIFont;

        private Texture2D plaid_bg;

        private ViewCamera theCamera;

        #region Input
        private KeyboardState keybState;
        private MouseState currentState;

        private float MOVE_SENSITIVITY = 0.01f;
        #endregion

        #region Block and Vertex Data
        Block[] blockArray;
        List<VertexPositionColor> vertexList;
        List<short> indexList;
        int vertexCount = 0;

        bool dirty = true;

        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;

        BasicEffect blockEffect;
        BasicEffect boundingEffect;

        Vector3 currentBlock = Vector3.One * -1;
        #endregion

        FPSCounter fpsCounter;

        protected override void Initialize()
        {
            Mouse.WindowHandle = this.Handle;

            Content = new ContentManager(Services, "VoxelModelContent");

            spriteBatch = new SpriteBatch(GraphicsDevice);

            UIFont = Content.Load<SpriteFont>("Fonts/HUDFont");

            plaid_bg = Content.Load<Texture2D>("Textures/plaid_bg");

            blockEffect = new BasicEffect(GraphicsDevice);
            boundingEffect = new BasicEffect(GraphicsDevice);

            blockArray = new Block[16*16*128];

            vertexList = new List<VertexPositionColor>();
            indexList = new List<short>();

            theCamera = new ViewCamera(GraphicsDevice.Viewport.AspectRatio, new Vector3(0, 0, 5), Vector3.Up, Vector3.Forward);

            theCamera.Initialize();

            createNew();

            fpsCounter = new FPSCounter();

            Application.Idle += delegate { Invalidate(); };
        }

        protected override void Draw()
        {
            fpsCounter.Update();

            keybState = Keyboard.GetState();
            currentState = Mouse.GetState();

            int centerX = GraphicsDevice.Viewport.Width / 2;
            int centerY = GraphicsDevice.Viewport.Height / 2;

            Vector3 moveVector = Vector3.Zero;

            if (keybState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.W))
                moveVector += theCamera.Forward;

            if (keybState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.S))
                moveVector -= theCamera.Forward;

            if (keybState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.A))
                moveVector -= theCamera.Right;

            if (keybState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.D))
                moveVector += theCamera.Right;

            if (keybState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Q))
                moveVector -= Vector3.Up;

            if (keybState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.E))
                moveVector += Vector3.Up;

            moveVector *= MOVE_SENSITIVITY;

            theCamera.Position += moveVector;

            theCamera.Update(Vector3.Zero, -(currentState.X - centerX), -(currentState.Y - centerY));

            Mouse.SetPosition(centerX, centerY);

            currentBlock = Vector3.One * -1;

            for (float i = 0; i < 25; i += 0.5f)
            {
                Vector3 target = theCamera.Position + (theCamera.Forward * i);

                if (BlockAt((int)target.X, (int)target.Y, (int)target.Z).Visible)
                {
                    currentBlock = new Vector3((int)target.X, (int)target.Y, (int)target.Z);

                    break;
                }
            }

            if (dirty)
                Build();

            GraphicsDevice.Clear(new Color(200, 200, 200));

            /*
            spriteBatch.Begin();

            for (int x = 0; x < GraphicsDevice.Viewport.Width + 64; x += 64)
            {
                for (int y = 0; y < GraphicsDevice.Viewport.Height + 64; y += 64)
                {
                    spriteBatch.Draw(plaid_bg, new Vector2(x, y), Color.White);
                }
            }
            
            spriteBatch.End();*/

            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.BlendState = BlendState.Opaque;

            GraphicsDevice.RasterizerState = RasterizerState.CullNone;
            //GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            //GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;

            blockEffect.World = Matrix.Identity;
            blockEffect.View = theCamera.View;
            blockEffect.Projection = theCamera.Projection;

            blockEffect.Alpha = 1.0f;
            blockEffect.VertexColorEnabled = true;

            foreach (EffectPass pass in blockEffect.CurrentTechnique.Passes)
            {
                pass.Apply();

                GraphicsDevice.SetVertexBuffer(vertexBuffer);
                GraphicsDevice.Indices = indexBuffer;

                try
                {
                    GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertexBuffer.VertexCount, 0, indexBuffer.IndexCount / 3);
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message);
                }
            }

            if (currentBlock.X >= 0 && currentBlock.X < 16 && currentBlock.Y >= 0 && currentBlock.Y < 128 && currentBlock.Z >= 0 && currentBlock.Z < 16)
            {
                DrawBoundingBox(currentBlock, Color.Gold);
            }

            spriteBatch.Begin();

            spriteBatch.DrawString(UIFont, "FPS: " + fpsCounter.frameRate, Vector2.Zero, Color.Yellow, 0.0f, Vector2.Zero, 0.5f, SpriteEffects.None, 0);
            spriteBatch.DrawString(UIFont, "Position: " + theCamera.Position, new Vector2(0, UIFont.LineSpacing), Color.White);
            
            spriteBatch.End();
        }

        private void DrawBoundingBox(Vector3 position, Color color)
        {
            Vector3 min = position;
            Vector3 max = position + Vector3.One;

            VertexPositionColor[] cubeLineVertices = new VertexPositionColor[8];
            cubeLineVertices[0] = new VertexPositionColor(min, color);
            cubeLineVertices[1] = new VertexPositionColor(new Vector3(max.X, min.Y, min.Z), color);
            cubeLineVertices[2] = new VertexPositionColor(new Vector3(max.X, min.Y, max.Z), color);
            cubeLineVertices[3] = new VertexPositionColor(new Vector3(min.X, min.Y, max.Z), color);

            cubeLineVertices[4] = new VertexPositionColor(new Vector3(min.X, max.Y, min.Z), color);
            cubeLineVertices[5] = new VertexPositionColor(new Vector3(max.X, max.Y, min.Z), color);
            cubeLineVertices[6] = new VertexPositionColor(max, color);
            cubeLineVertices[7] = new VertexPositionColor(new Vector3(min.X, max.Y, max.Z), color);

            short[] cubeLineIndices = { 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 };

            boundingEffect.World = Matrix.Identity;
            boundingEffect.View = theCamera.View;
            boundingEffect.Projection = theCamera.Projection;

            boundingEffect.VertexColorEnabled = true;

            foreach (EffectPass pass in boundingEffect.CurrentTechnique.Passes)
            {
                pass.Apply();

                GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.LineList, cubeLineVertices, 0, 8, cubeLineIndices, 0, 12);
            }
        }

        /// <summary>
        /// Creates a new project
        /// </summary>
        private void createNew()
        {
            // create the default cube vertices

            for (int x = 1; x < 15; x++)
            {
                for (int z = 1; z < 15; z++)
                {
                    for (int y = 0; y < 128; y++)
                    {
                        if(y % 2 == 0)
                            SetBlock(x, y, z, new Block(Color.Red));
                    }
                }
            }

            Build();
        }

        #region Block Build
        public void Build()
        {
            vertexList.Clear();
            indexList.Clear();
            vertexCount = 0;

            for(int x = 0; x < 16; x++)
            {
                for(int z = 0; z < 16; z++)
                {
                    int offs = x * 16 * 128 + z * 128;

                    for(int y = 0; y < 128; y++)
                    {
                        if(blockArray[offs+y].Visible)
                            BuildBlock(blockArray[offs + y], new Vector3(x, y, z));
                    }
                }
            }

            VertexPositionColor[] vertexData = vertexList.ToArray();
            short[] indices = indexList.ToArray();

            if (vertexData.Length > 0)
            {
                vertexBuffer = new DynamicVertexBuffer(GraphicsDevice, typeof(VertexPositionColor), vertexData.Length, BufferUsage.WriteOnly);
                vertexBuffer.SetData(vertexData);

                indexBuffer = new IndexBuffer(GraphicsDevice, IndexElementSize.SixteenBits, indices.Length, BufferUsage.WriteOnly);
                indexBuffer.SetData(indices);
            }

            dirty = false;
        }

        public void BuildBlock(Block block, Vector3 position)
        {
            int X = (int)position.X, Y = (int)position.Y, Z = (int)position.Z;

            Block left = BlockAt(X - 1, Y, Z);
            Block right = BlockAt(X + 1, Y, Z);
            Block bottom = BlockAt(X, Y - 1, Z);
            Block top = BlockAt(X, Y + 1, Z);
            Block forward = BlockAt(X, Y, Z + 1);
            Block back = BlockAt(X, Y, Z - 1);

            if (!left.Visible)
            {
                BuildFace(X, Y, Z, 1, block);
            }

            if (!right.Visible)
            {
                BuildFace(X, Y, Z, 0, block);
            }

            if (!bottom.Visible)
            {
                BuildFace(X, Y, Z, 2, block);
            }

            if (!top.Visible)
            {
                BuildFace(X, Y, Z, 3, block);
            }

            if (!back.Visible)
            {
                BuildFace(X, Y, Z, 5, block);
            }

            if (!forward.Visible)
            {
                BuildFace(X, Y, Z, 4, block);
            }
        }

        public void BuildFace(int x, int y, int z, int dir, Block block)
        {
            Vector3 pos = new Vector3(x, y, z);

            switch (dir)
            {
                case 0:
                    AddVertex(pos, new Vector3(1, 1, 1), block.Color);
                    AddVertex(pos, new Vector3(1, 1, 0), block.Color);
                    AddVertex(pos, new Vector3(1, 0, 1), block.Color);
                    AddVertex(pos, new Vector3(1, 0, 0), block.Color);

                    AddIndex(0, 1, 2, 2, 1, 3);
                    break;
                case 1:
                    AddVertex(pos, new Vector3(0, 1, 0), block.Color);
                    AddVertex(pos, new Vector3(0, 1, 1), block.Color);
                    AddVertex(pos, new Vector3(0, 0, 0), block.Color);
                    AddVertex(pos, new Vector3(0, 0, 1), block.Color);

                    AddIndex(0, 1, 3, 0, 3, 2);
                    break;
                case 2:
                    AddVertex(pos, new Vector3(1, 1, 1), block.Color);
                    AddVertex(pos, new Vector3(0, 1, 1), block.Color);
                    AddVertex(pos, new Vector3(1, 1, 0), block.Color);
                    AddVertex(pos, new Vector3(0, 1, 0), block.Color);

                    AddIndex(3, 2, 0, 3, 0, 1);
                    break;
                case 3:
                    AddVertex(pos, new Vector3(1, 0, 1), block.Color);
                    AddVertex(pos, new Vector3(0, 0, 1), block.Color);
                    AddVertex(pos, new Vector3(1, 0, 0), block.Color);
                    AddVertex(pos, new Vector3(0, 0, 0), block.Color);

                    AddIndex(0, 2, 1, 1, 2, 3);
                    break;
                case 4:
                    AddVertex(pos, new Vector3(0, 1, 1), block.Color);
                    AddVertex(pos, new Vector3(1, 1, 1), block.Color);
                    AddVertex(pos, new Vector3(0, 0, 1), block.Color);
                    AddVertex(pos, new Vector3(1, 0, 1), block.Color);

                    AddIndex(0, 1, 3, 0, 3, 2);
                    break;
                case 5:
                    AddVertex(pos, new Vector3(1, 1, 0), block.Color);
                    AddVertex(pos, new Vector3(0, 1, 0), block.Color);
                    AddVertex(pos, new Vector3(1, 0, 0), block.Color);
                    AddVertex(pos, new Vector3(0, 0, 0), block.Color);

                    AddIndex(0, 1, 2, 2, 1, 3);
                    break;
            }
        }

        public void AddVertex(Vector3 pos, Vector3 vertAdd, Color color)
        {
            vertexList.Add(new VertexPositionColor(pos + vertAdd, color));
        }

        public void AddIndex(short s1, short s2, short s3, short s4, short s5, short s6)
        {
            indexList.Add((short)(vertexCount + s1));
            indexList.Add((short)(vertexCount + s2));
            indexList.Add((short)(vertexCount + s3));
            indexList.Add((short)(vertexCount + s4));
            indexList.Add((short)(vertexCount + s5));
            indexList.Add((short)(vertexCount + s6));
            vertexCount += 4;
        }
#endregion

        #region Block Access
        public Block BlockAt(int x, int y, int z)
        {
            int index = x * 16 * 128 + z * 128 + y;

            if (index < 0 || index >= blockArray.Length)
                return new Block(false);

            return blockArray[index];
        }

        public void SetBlock(Vector3 position, Block color)
        {
            SetBlock((int)position.X, (int)position.Y, (int)position.Z, color);
        }

        public void SetBlock(int x, int y, int z, Block color)
        {
            int index = x * 16 * 128 + z * 128 + y;

            blockArray[index] = color;

            dirty = true;
        }
        #endregion
    }
}
